package com.espirit.eap.manager;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.orm.jpa.EntityManagerHolder;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.StringUtils;

import com.googlecode.cswish.annotation.Protected;
import com.googlecode.cswish.annotation.Rest;
import com.googlecode.cswish.exception.ApplicationException;
import com.googlecode.cswish.model.PageInfo;
import com.googlecode.cswish.struts.ContextHolder;
import com.googlecode.cswish.struts.hibernate.DynamicClassLoaderFactory;
import com.googlecode.cswish.struts.hibernate.HibernateConfiguration;
import com.googlecode.cswish.struts.spring.CacheService;
import com.googlecode.cswish.struts.spring.GenericService;
import com.googlecode.cswish.struts.spring.ObjectFactory;
import com.googlecode.cswish.util.InvocationUtil;
import com.googlecode.cswish.util.InvocationUtil.ServiceInfo;

@Service
public class TaskSchedulerService {

	private static final Log logger = LogFactory.getLog(TaskSchedulerService.class);
	
	@Resource
	@Qualifier(value="appScheduler")
	private ThreadPoolTaskScheduler taskScheduler;
	
	@Resource
	private InvocationUtil invocationUtil;
	
	@Resource
	private GenericService genericService;
	
	@Resource
    private ObjectFactory objectFactory;
	
	@Resource
	private CacheService cacheService;
	
	@Resource
	private DynamicClassLoaderFactory classLoaderFactory;
	
	@Resource
	private HibernateConfiguration configuration;
	
	private Map<String, ScheduledFuture> scheduledFuture;
	
	@PostConstruct
	private void init() {
		scheduledFuture = cacheService.newCache(ScheduledFuture.class);
	}
	
	@Protected
	public TaskScheduler checkAndAdd(TaskScheduler model) {
		TaskScheduler example = new TaskScheduler();
		example.setName(model.getName());
		PageInfo<TaskScheduler> pageInfo = genericService.searchAll(example);
		if (pageInfo.getData().size() > 0) {
			return pageInfo.getData(0);
		}
		
		return add(model);
	}
	
	@Rest(type="_add")
    public TaskScheduler add(TaskScheduler model) {
		checkCommand(model.getCond(), model.getCommand());
		
		TaskScheduler dbModel = genericService.add(model);
		
		if (model.getActive() != null && model.getActive()) {
			runScheduler(dbModel, ContextHolder.get().getSiteName());
		}
		return dbModel;
	}
	
	private void checkCommand(String cond, String command) {
		if (!NumberUtils.isDigits(cond)) {
			String[] fields = StringUtils.tokenizeToStringArray(cond, " ");
			if (fields.length != 6) {
				throw new ApplicationException("无效的条件", cond);
			}
		}
		
		int index = command.lastIndexOf('.');
		String serviceName = command.substring(0, index);
		String methodName = command.substring(index + 1);
		ServiceInfo serviceInfo = invocationUtil.getServiceInfo(serviceName, methodName, false);
		if (serviceInfo == null) {
			throw new ApplicationException("errors.invalidCommand", command);
		}
	}
	
	@Protected
	public ScheduledFuture runScheduler(TaskScheduler model, final String siteName) {
		String taskId = model.getId() == null ? model.getName() : String.valueOf(model.getId());
		ScheduledFuture scheduler = scheduledFuture.get(taskId);
		if (scheduler != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Scheduler is running, " + model.getName());
			}
			return scheduler;
		}
		
		if (logger.isDebugEnabled()) {
			logger.debug(siteName + " startups schedule '" + model.getName() + "' at " 
				+ model.getCond() + ", command is " + model.getCommand());
		}
		
		String cond = model.getCond();
		Trigger trigger;
		if (NumberUtils.isDigits(cond)) {
			long fixRate = new Long(cond);
			trigger = new PeriodicTrigger(fixRate);
		} else {
			trigger = new CronTrigger(cond);
		}
		
		String command = model.getCommand();
		int index = command.lastIndexOf('.');
		String serviceName = command.substring(0, index);
		String methodName = command.substring(index + 1);
		
		final ServiceInfo serviceInfo = invocationUtil.getServiceInfo(serviceName, methodName, false);
		if (serviceInfo == null) {
			throw new ApplicationException("errors.invalidCommand", command);
		}
		
		scheduler = taskScheduler.schedule(new Runnable() {
			@Override
			public void run() {
				try {
					ContextHolder.get().setSiteName(siteName);
					classLoaderFactory.bindSiteClassLoader();
					
					EntityManagerFactory emf = configuration.getEntityManagerFactory();
					EntityManager em = emf.createEntityManager();
					TransactionSynchronizationManager.bindResource(emf,
							new EntityManagerHolder(em));
					
					// TODO: move getBean to the outside of the method?
					Object instance = objectFactory.getBean(serviceInfo.serviceType.getName(), false);
					Method method = serviceInfo.serviceMethod;
					method.invoke(instance);
				} catch (Exception ex) {
					logger.error("Run Scheduler", ex);
				} finally {
					EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager
							.unbindResource(configuration.getEntityManagerFactory());
					EntityManagerFactoryUtils.closeEntityManager(emHolder
							.getEntityManager());
					classLoaderFactory.unbindSiteClassLoader();
				}
			}
		}, trigger);
		
		scheduledFuture.put(taskId, scheduler);
		return scheduler;
	}
	
	public TaskScheduler update(TaskScheduler model) {
		checkCommand(model.getCond(), model.getCommand());
		
		TaskScheduler oldModel = genericService.load(model);
		// stop old schedule
		stopScheduler(oldModel);
		
		TaskScheduler dbModel = genericService.update(model);
		
		if (dbModel.getActive() != null && dbModel.getActive()) {
			// start new schedule
			runScheduler(dbModel, ContextHolder.get().getSiteName());
		}
		return dbModel;
	}
	
	public void run(TaskScheduler model) {
		model = genericService.load(model);
		
		String command = model.getCommand();
		int index = command.lastIndexOf('.');
		String serviceName = command.substring(0, index);
		String methodName = command.substring(index + 1);
		
		final ServiceInfo serviceInfo = invocationUtil.getServiceInfo(serviceName, methodName, false);
		if (serviceInfo == null) {
			throw new ApplicationException("errors.invalidCommand", command);
		}
		Object instance = objectFactory.getBean(serviceInfo.serviceType.getName(), false);
		Method method = serviceInfo.serviceMethod;
		try {
			method.invoke(instance);
		} catch (Exception ex) {
			logger.error("Run Command", ex);
		}
	}

	public void stopScheduler(TaskScheduler model) {
		String taskId = model.getId() == null ? model.getName() : String.valueOf(model.getId());
		ScheduledFuture future = scheduledFuture.get(taskId);
		if (future != null) {
			if (!future.isDone()) {
				future.cancel(true);
			}
			scheduledFuture.remove(taskId);
			
			if (logger.isDebugEnabled()) {
				logger.debug(ContextHolder.get().getSiteName() + " stops schedule '" + model.getName() + "'");
			}
		}
	}
	
    public boolean deleteList(List<TaskScheduler> results) {
    	boolean ret = true;
    	if (results != null) {
			for (TaskScheduler theModel : results) {
				if (theModel != null) {
					ret &= delete(theModel);
				}
			}
    	}
    	return ret;
    }

    public boolean delete(TaskScheduler model) {
    	TaskScheduler oldModel = genericService.load(model);
    	
    	boolean ret = genericService.delete(model);
    	
    	if (ret) {
    		stopScheduler(oldModel);
    	}
    	
    	return ret;
    }
}